package com.platform.core.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.platform.core.entity.QSysConfType;
import com.platform.core.repository.SysConfRepository;
import com.platform.core.repository.SysConfTypeRepository;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import com.platform.comm.enums.YnEnum;
import com.platform.comm.util.JSONUtil;
import com.platform.dto.ConfQo;
import com.platform.core.entity.QSysConf;
import com.platform.core.entity.SysConf;
import com.platform.core.entity.SysConfType;
import com.platform.enums.ConfValueMode;
import com.platform.core.service.SysConfService;
import com.platform.comm.dto.DtoMapper;
import com.platform.comm.exceptions.RestApiException;
import com.platform.utils.Pair;
import com.platform.utils.SysConfUtil;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFactory;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QSort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * Description: 参数配置-Service实现类
 *
 * @author libin Created on 2019/7/19
 **/
@Slf4j
@Service
public class SysConfServiceImpl extends BaseEntityBizImpl<SysConfRepository, SysConf> implements SysConfService {
    private static final String ROUTE_SEPARATOR = "-";
    private final QSysConf qSysConf = QSysConf.sysConf;
    private final DtoMapper dtoMapper;
    private final SysConfTypeRepository sysConfTypeRepository;
    public SysConfServiceImpl(SysConfRepository baseRepository, SysConfTypeRepository sysConfTypeRepository, DtoMapper dtoMapper) {
        super(baseRepository);
        this.sysConfTypeRepository = sysConfTypeRepository;
        this.dtoMapper = dtoMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ConfQo create(ConfQo confQo) {
        confQo.init();
        SysConf entity = dtoMapper.map(confQo, SysConf.class);
        // 设置routeCode
        SysConfType sysConfType = sysConfTypeRepository.findById(confQo.getTypeId())
                .orElseThrow(() -> new RestApiException("未找到类别【"+ confQo.getTypeId() +"】"));
        entity.setRouteCode(sysConfType.getRoute() + ROUTE_SEPARATOR + confQo.getConfCode());
        //考虑参数设置以及状态规范等其他模块，根据valueMapping，赋值confValue
        if (Objects.nonNull(entity.getValueMapping()) && ConfValueMode.InSingle != entity.getValueMode()){
            entity.setConfValue(getConfValueByMapping(entity.getValueMapping()));
        }
        entity.setCreateDate(LocalDateTime.now());
        entity.setUpdateDate(LocalDateTime.now());
        saveEntity(entity);
        return dtoMapper.map(entity, ConfQo.class);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ConfQo createSysDict(ConfQo confQo) {
        SysConfType sysConfType = sysConfTypeRepository.findOne(QSysConfType.sysConfType.route.eq("clz-conf-dictGroup-system"))
                .orElseThrow(() -> new RuntimeException("没有匹配到对应的参数类别"));
        confQo.setTypeId(sysConfType.getId());
        return this.create(confQo);
    }


    /**
     * 根据valueMapping，赋值confValue
     * @param valueMapping 映射关系
     */
    public String getConfValueByMapping(JsonNode valueMapping){
        List<String> valueKeys = Lists.newArrayList();
        StreamSupport.stream(Spliterators.spliteratorUnknownSize(valueMapping.elements(), Spliterator.ORDERED), false).forEach(jsonNode ->{
            valueKeys.add(jsonNode.findValuesAsText(SysConfUtil.KEY).get(0));
        });
        JsonNodeFactory factory = JsonNodeFactory.instance;
        ObjectNode node = new ObjectNode(factory);
        node.putPOJO(SysConfUtil.VALUE,valueKeys);

        return cn.hutool.json.JSONUtil.toJsonStr(node);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ConfQo modify(ConfQo confQo) {
        SysConf entity = fetchById(confQo.getId());
        entity.setUpdateDate(LocalDateTime.now());
        //判断mapping有无调整
        JsonNode oldValueMapping = entity.getValueMapping();
        JsonNode newValueMapping = confQo.getValueMapping();
        //判断confCode有无调整
        String oldConfCode = entity.getConfCode();
        String newConfCode = confQo.getConfCode();
        //判断所属类别是否调整
        String oldTypeId = entity.getTypeId();
        String newTypeId = confQo.getTypeId();
        if (!Objects.equals(oldTypeId,newTypeId)){
            SysConfType oldSysConfType = sysConfTypeRepository.findById(oldTypeId)
                    .orElseThrow(() -> new RestApiException("未找到类别【"+ oldTypeId +"】"));
            SysConfType newSysConfType = sysConfTypeRepository.findById(newTypeId)
                    .orElseThrow(() -> new RestApiException("未找到类别【"+ newTypeId +"】"));
            String routeCode = entity.getRouteCode();
            String s = routeCode.replaceAll(oldSysConfType.getRoute(), newSysConfType.getRoute());
            confQo.setRouteCode(s);
        }
        if (Objects.equals(oldValueMapping,newValueMapping) && Objects.equals(oldConfCode,newConfCode)){
            dtoMapper.map(confQo, entity);
            saveEntity(entity);
            return dtoMapper.map(entity, ConfQo.class);
        }
        if (!Objects.equals(oldValueMapping,newValueMapping) && ConfValueMode.InMulti.equals(confQo.getValueMode())){
            String confValue = getConfValueByMapping(newValueMapping);
            JSONObject obj = cn.hutool.json.JSONUtil.parseObj(confValue);
            confQo.setConfValue(obj);
        }else if (!Objects.equals(oldValueMapping,newValueMapping) && ConfValueMode.InSingle.equals(confQo.getValueMode())){
            JsonNodeFactory factory = JsonNodeFactory.instance;
            JSONObject obj = new JSONObject();
            obj.append(SysConfUtil.VALUE,"");
            confQo.setConfValue(obj);
        }
        if (!Objects.equals(oldConfCode,newConfCode)){
            String routeCode = entity.getRouteCode();
            String s = routeCode.replaceAll(oldConfCode, newConfCode);
            confQo.setRouteCode(s);
        }
        dtoMapper.map(confQo, entity);
        saveEntity(entity);
        return dtoMapper.map(entity, ConfQo.class);
    }

    @Override
    public Page<ConfQo> queryConfs(Predicate predicate, Pageable pageable, MultiValueMap<String, String> parameters) {
        BooleanBuilder builder = this.getBooleanBuilderSerch(parameters);
        pageable = PageRequest.of(pageable.getPageNumber(),pageable.getPageSize(),QSort.by(QSysConf.sysConf.updateDate.desc(),QSysConf.sysConf.sortNum.asc()));
        Page<SysConf> page = queryByPage(builderConditions(builder, parameters, SysConf.class, qSysConf.getMetadata()), pageable);
        return page.map(entity -> dtoMapper.map(entity, ConfQo.class));
    }

    @Override
    public Page<ConfQo> searchDictGroup(Predicate predicate, Pageable pageable, MultiValueMap<String, String> parameters) {
        String dictGroupPath = "clz-conf-dictGroup";

        // 根据应用分组查询
        String appCode = parameters.getFirst("appCode");
        String dictType = parameters.getFirst("dictType");
        assert dictType != null;
        //拼接查询条件
        String dictRoute = dictGroupPath.concat("-" + dictType).concat("%");
        if (StringUtils.isNotEmpty(appCode)) {
            dictRoute = dictGroupPath.concat("-" + dictType + "-").concat(appCode).concat("%");
        }
        BooleanBuilder builder = new BooleanBuilder();
        if ("dictGroup".equals(dictType)) {
            builder.and(qSysConf.routeCode.like(dictGroupPath + "%"));
        } else {
            builder.and(qSysConf.routeCode.like(dictRoute));
        }
        //查询条件字典名称、字典编码
        String confName = parameters.getFirst("confName");
        if (StringUtils.isNotBlank(confName)) {
            builder.and(qSysConf.confName.contains(confName));
        }
        String confCode = parameters.getFirst("confCode");
        if (StringUtils.isNotBlank(confCode)) {
            builder.and(qSysConf.confCode.contains(confCode));
        }

        pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), QSort.by(QSysConf.sysConf.updateDate.desc(), QSysConf.sysConf.sortNum.asc()));
        Page<SysConf> page = queryByPage(builderConditions(builder, parameters, SysConf.class, qSysConf.getMetadata()), pageable);
        DtoMapper dtoMapper1 = new DtoMapper() {
            @Override
            protected void configure(MapperFactory factory) {
                factory.classMap(SysConf.class, ConfQo.class)
                        .exclude("confValue").exclude("valueMapping")
                        .byDefault()
                        .register();
            }
        };
        return page.map(entity -> {
            ConfQo map = dtoMapper1.map(entity, ConfQo.class);
            map.setValueMapping(entity.getValueMapping());
            return map;
        });
    }

    @Override
    public List<ConfQo> queryConfs(Predicate predicate, MultiValueMap<String, String> parameters) {
        BooleanBuilder builder = this.getBooleanBuilder(parameters);
        builder.and(qSysConf.delFlag.eq(YnEnum.N));
        return queryAll(builder).stream().map(entity -> dtoMapper.map(entity,ConfQo.class)).collect(Collectors.toList());
    }

    public BooleanBuilder getBooleanBuilder(MultiValueMap<String, String> parameters){
        BooleanBuilder builder = new BooleanBuilder(qSysConf.typeId.eq(parameters.getFirst("typeId")));
        String confCode = parameters.getFirst("confCode");
        if(StringUtils.isNotBlank(confCode)){
            builder.and(qSysConf.confCode.eq(confCode));
        }
        String confName = parameters.getFirst("confName");
        if(StringUtils.isNotBlank(confName)){
            builder.and(qSysConf.confName.contains(confName));
        }
        return builder;
    }
    public BooleanBuilder getBooleanBuilderSerch(MultiValueMap<String, String> parameters){
        String typeId = parameters.getFirst("typeId");
        BooleanBuilder builder = new BooleanBuilder();
        if (StringUtils.isNotEmpty(typeId)) {
            SysConfType sysConfType = sysConfTypeRepository.findById(typeId)
                    .orElseThrow(() -> new RestApiException("未找到类别【"+ typeId +"】"));
            builder.and(qSysConf.routeCode.like(sysConfType.getRoute()+"-%"));
        }
        String confCode = parameters.getFirst("confCode");
        if(StringUtils.isNotBlank(confCode)){
            builder.and(qSysConf.confCode.contains(confCode));
        }
        String confName = parameters.getFirst("confName");
        if(StringUtils.isNotBlank(confName)){
            builder.and(qSysConf.confName.contains(confName));
        }
        String routePrefix = parameters.getFirst("routePrefix");
        if(StringUtils.isNotEmpty(routePrefix)){
            builder.and(qSysConf.routeCode.contains(routePrefix));
        }
        String valueMode = parameters.getFirst("valueMode");
        if (StringUtils.isNotEmpty(valueMode)) {
            builder.and(qSysConf.valueMode.in(ConfValueMode.InMulti,ConfValueMode.InSingle));
        }

        return builder;
    }
    @Override
    public void batchRemove(List<String> ids) {
        ids.forEach(repository::deleteById);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long checkCodeExists(String id, String confCode,String typeId) {
        BooleanBuilder builder = buildValidConditions(id);
        builder.and(qSysConf.confCode.eq(confCode));
        //增加系统信息
//        builder.and(qSysConf.typeId.eq(typeId));
        return repository.count(builder);
    }

    @Override
    public Long checkNameExists(String id, String confName,String typeId) {
        BooleanBuilder builder = buildValidConditions(id);
        builder.and(qSysConf.confName.eq(confName));
//        builder.and(qSysConf.typeId.eq(typeId));
        return repository.count(builder);
    }

    private BooleanBuilder buildValidConditions(String id) {
        BooleanBuilder builder = new BooleanBuilder();
        if (!Strings.isNullOrEmpty(id)) {
            builder.and(qSysConf.id.ne(id));
        }
        return builder;
    }

    @Override
    public ConfQo queryByCode(String confCode) {
        return queryOne(qSysConf.confCode.eq(confCode).and(qSysConf.delFlag.eq(YnEnum.N)))
                .map(entity -> {
                    ConfQo confQo = dtoMapper.map(entity, ConfQo.class);
                    try {
                        if (Objects.equals(confQo.getValueMode().getValue(),"2") ||
                                Objects.equals(confQo.getValueMode().getValue(),"3")  ){
                            List<Pair<String, Object>> valueMappingList =  JSONUtil.JSONObjectToObject(entity.getValueMapping(),
                                    new TypeReference<List<Pair<String, Object>>>(){});
                            confQo.setValueMappingList(valueMappingList);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return confQo;
                })
                .orElse(null);
    }

    @Override
    public List<JsonNode> getConfInfo(String code) {
        ConfQo confQo = queryOne(qSysConf.confCode.eq(code).and(qSysConf.delFlag.eq(YnEnum.N)))
                .map(entity -> dtoMapper.map(entity, ConfQo.class)).orElse(null);
        Assert.notNull(confQo,"根据参数编码未查询到有效的配置信息！");
        JsonNode valueMapping = confQo.getValueMapping();
        JSONObject confValue = confQo.getConfValue();
        List<JsonNode> key = null;
        //分别处理多选和单选场景
//        if (Objects.nonNull(confValue.get(SysConfUtil.VALUE)) && ConfValueMode.InMulti.getValue().equals(confQo.getValueMode().getValue())){
//            List<String> values = StreamSupport.stream(Spliterators.spliteratorUnknownSize(confValue.get(SysConfUtil.VALUE).iterator(), Spliterator.ORDERED), false)
//                    .map(JsonNode::asText).collect(Collectors.toList());
//            key = StreamSupport.stream(Spliterators.spliteratorUnknownSize(valueMapping.elements(), Spliterator.ORDERED), false)
//                    .filter(jsonNode -> values.contains(jsonNode.findValuesAsText(SysConfUtil.KEY).get(0))).collect(Collectors.toList());
//        }else if (Objects.nonNull(confValue.get(SysConfUtil.VALUE)) && ConfValueMode.InSingle.getValue().equals(confQo.getValueMode().getValue())){
//            String singleKey = confValue.findValuesAsText(SysConfUtil.VALUE).get(0);
//            key = StreamSupport.stream(Spliterators.spliteratorUnknownSize(valueMapping.elements(), Spliterator.ORDERED), false)
//                    .filter(jsonNode -> singleKey.equals(jsonNode.findValuesAsText(SysConfUtil.KEY).get(0))).collect(Collectors.toList());
//        }
        return key;
    }

    @Override
    public String getSingleConfInfo(String code) {
        List<JsonNode> confInfo = this.getConfInfo(code);
        if (Objects.nonNull(confInfo)){
            JsonNode jsonNode = confInfo.get(0);
            return jsonNode.findValuesAsText(SysConfUtil.VALUE).get(0);
        }else{
            ConfQo confQo = queryOne(qSysConf.confCode.eq(code).and(qSysConf.delFlag.eq(YnEnum.N)))
                    .map(entity -> dtoMapper.map(entity, ConfQo.class)).orElse(null);
            Assert.notNull(confQo,"根据参数编码未查询到有效的配置信息！");
//            ObjectNode confValue = confQo.getConfValue();
//            List<String> valuesAsText = confValue.findValuesAsText(SysConfUtil.VALUE);
            return null;
        }
    }

    @Override
    public Map<String,Object> getSingleConfInfo(String groupCode,String itemCode) {
        List<JsonNode> confInfo = this.getConfInfo(groupCode);
        assert confInfo != null;
        List<Map<String, Object>> valueMappingList = null;
        try {
            valueMappingList = JSONUtil.JSONObjectToObject(confInfo,
                    new TypeReference<List<Map<String, Object>>>(){});
        } catch (IOException e) {
            e.printStackTrace();
        }
        assert valueMappingList != null;
        List<Map<String, Object>> collect = valueMappingList.stream().filter(item -> Objects.equals(item.get(SysConfUtil.KEY), itemCode))
                .collect(Collectors.toList());
        Map<String,Object> map = Maps.newHashMap();
        if (collect.size() == 0){
            Map<String, Object> objectMap = Maps.newHashMap();
            objectMap.put(itemCode,itemCode);
            return objectMap;
        }
        Map<String, Object> stringObjectMap = collect.get(0);
        map.put(stringObjectMap.get(SysConfUtil.KEY).toString(),stringObjectMap.get(SysConfUtil.VALUE));
        return map;
    }

    @Override
    public Object getSingleValue(String groupCode,String itemCode) {
        Map<String, Object> singleConfInfo = getSingleConfInfo(groupCode, itemCode);
        return singleConfInfo.get(itemCode);
    }

    @Override
    public List<Map<String, Object>> getDictConfInfo(String groupCode) {
        List<JsonNode> confInfo = this.getConfInfo(groupCode);
        List<Map<String, Object>> list = Lists.newArrayList();
        confInfo.forEach(conf ->{
            try {
                Map<String, Object> map = JSONUtil.JSONObjectToObject(conf, new TypeReference<Map<String, Object>>(){});
                list.add(map);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return list;
    }
    @Override
    public Map<String, List<Map<String, Object>>> searchDictConfInfos(List<String> list) {
        Map<String, List<Map<String, Object>>> map = Maps.newHashMap();
        final List[] resultList = new List[]{Lists.newArrayList()};
        list.forEach(groupCode ->{
            List<JsonNode> confInfo = this.getConfInfo(groupCode);
            resultList[0] = Lists.newArrayList();
            confInfo.forEach(conf ->{
                try {
                    Map<String, Object> maps = JSONUtil.JSONObjectToObject(conf, new TypeReference<Map<String, Object>>(){});
                    resultList[0].add(maps);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            map.put(groupCode, resultList[0]);
        });
        return map;
    }

    @Override
    public ConfQo findConfByRouteCode(String routeCode){
        return queryOne(qSysConf.routeCode.eq(routeCode).and(qSysConf.delFlag.eq(YnEnum.N)))
                .map(entity -> {
                    ConfQo confQo = dtoMapper.map(entity, ConfQo.class);
                    try {
                        if (Objects.equals(confQo.getValueMode().getValue(),"2") ||
                                Objects.equals(confQo.getValueMode().getValue(),"3")  ){
                            List<Pair<String, Object>> valueMappingList =  JSONUtil.JSONObjectToObject(entity.getValueMapping(),
                                    new TypeReference<List<Pair<String, Object>>>(){});
                            confQo.setValueMappingList(valueMappingList);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return confQo;
                })
                .orElse(null);
    }

    @Override
    public List<Map<String, String>> getConfList(String code) {
        List<Map<String, String>> keys = new ArrayList<>();
        List<JsonNode> nodes = getConfInfo(code);
        nodes.forEach(node -> {
            Map<String, String> map = new HashMap<>(4);
            node.fieldNames().forEachRemaining(field -> map.put(field, node.get(field).textValue()));
            keys.add(map);
        });

        return keys;
    }
}
